home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
NOVA - For the NeXT Workstation
/
NOVA - For the NeXT Workstation.iso
/
SourceCode
/
AdobeExamples
/
NX_Clock
/
ClockView.m
< prev
next >
Wrap
Text File
|
1992-12-19
|
19KB
|
734 lines
/*
* (a) (C) 1990 by Adobe Systems Incorporated. All rights reserved.
*
* (b) If this Sample Code is distributed as part of the Display PostScript
* System Software Development Kit from Adobe Systems Incorporated,
* then this copy is designated as Development Software and its use is
* subject to the terms of the License Agreement attached to such Kit.
*
* (c) If this Sample Code is distributed independently, then the following
* terms apply:
*
* (d) This file may be freely copied and redistributed as long as:
* 1) Parts (a), (d), (e) and (f) continue to be included in the file,
* 2) If the file has been modified in any way, a notice of such
* modification is conspicuously indicated.
*
* (e) PostScript, Display PostScript, and Adobe are registered trademarks of
* Adobe Systems Incorporated.
*
* (f) THE INFORMATION BELOW IS FURNISHED AS IS, IS SUBJECT TO
* CHANGE WITHOUT NOTICE, AND SHOULD NOT BE CONSTRUED
* AS A COMMITMENT BY ADOBE SYSTEMS INCORPORATED.
* ADOBE SYSTEMS INCORPORATED ASSUMES NO RESPONSIBILITY
* OR LIABILITY FOR ANY ERRORS OR INACCURACIES, MAKES NO
* WARRANTY OF ANY KIND (EXPRESS, IMPLIED OR STATUTORY)
* WITH RESPECT TO THIS INFORMATION, AND EXPRESSLY
* DISCLAIMS ANY AND ALL WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR PARTICULAR PURPOSES AND NONINFRINGEMENT
* OF THIRD PARTY RIGHTS.
*/
/*
* ClockView.m
*
* This class handles the drawing of the clock and the moving of the alarm.
* The clock face is drawn into a bitmap and then composited into the buffered
* window before drawing the hands. The hands are stored in the server
* as user paths. Each hand also has a graphic state associated with it. Before
* hand is drawn, its graphic state is installed and then rotated to its current
* angle and then the user path is rendered.
*
* An animator object has been borrowed from the stopwatch implementation in
* the NeXTDeveloper directory. This object makes adjustments to the timed
* entry in order to keep the timing up to date.
*
* Version: 2.0
* Author: Ken Fromm
* History:
* 03-07-91 Added this comment.
*/
#import "Animator.h"
#import "ClockView.h"
#import "ClockViewWraps.h"
#import <appkit/Cell.h>
#import <appkit/Control.h>
#import <appkit/NXImage.h>
#import <appkit/View.h>
#import <appkit/nextstd.h>
#import <dpsclient/dpsclient.h>
#import <dpsclient/wraps.h>
@implementation ClockView
static void drawClockHand(id self, int hand);
/*
* These are the user path operands and operators for the clock hands.
* They are sent and stored in the server.
*/
static float ptsHour[] = { -10, -10, 10, 170, -4.5, 0, 0, 120, 0,120, 4.5, 180, 0, 0, -120,
0, 0, 0, 0, 10, 360, 0};
static char opsHour[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
dps_closepath, dps_moveto, dps_arcn, dps_closepath};
static float ptsMin[] = { -10, -10, 10, 175, -4.5, 0, 0, 162, 0,162, 4.5, 180, 0, 0, -162,
0, 0, 0, 0, 10, 360, 0};
static char opsMin[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
dps_closepath, dps_moveto, dps_arcn, dps_closepath};
static float ptsSec[] = { -10, -30, 10, 170, -1.5, 0, 0, 145, 3, 0, 0, -145,
4, 0, 0, -20, 0, -20, 5.5, 360, 180, 0, 20, 4, 0, 0, 0, 0, 0, 10, 360, 0};
static char opsSec[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_rlineto, dps_rlineto,
dps_rlineto, dps_rlineto, dps_arcn, dps_rlineto, dps_rlineto, dps_closepath,
dps_moveto, dps_arcn, dps_closepath};
static float ptsAlarmTop[] = { -5, 70, 5, 120, -1.0, 100, 0, 5, 0, 105, 1.0, 180, 0, 0, -5};
static char opsAlarmTop[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_arcn, dps_rlineto,
dps_closepath};
static float ptsAlarmBot[] = { -5, -2, 5, 120, -1.0, 0, 0, 100, 2.0, 0, 0, -100};
static char opsAlarmBot[] = {dps_setbbox, dps_moveto, dps_rlineto, dps_rlineto,
dps_rlineto, dps_closepath};
/*
* Initialize the instance variables and create an Animator object.
* The animator is used to keep the timed_entry up to date. Without
* the animator adjustments, the clock loses time. An array to hold
* the hit detection user path is allocated. This mouse location
* will be inserted into this user path before the hit detection is
* tested for.
*/
- initFrame:(const NXRect *) frameRect
{
[super initFrame:frameRect];
[self setClipping:NO];
gstatesOn = upathsServer = YES;
totalTime = numIterations = 0;
angleAlarm = 0;
gstateHour = gstateMin = gstateSec = gstateShad = 0;
upathHour = upathMin = upathSec = upathAlarmTop = upathAlarmBot = 0;
NX_MALLOC(hitPoint.pts, float, MAX_PTS_HIT);
NX_MALLOC(hitPoint.ops, char, MAX_OPS_HIT);
[self initializeHitPoint];
animatorId = [Animator newChronon: 1.0
adaptation: 3.0
target: self
action: @selector(tick:)
autoStart: NO
eventMask: NX_ALLEVENTS];
return self;
}
/* Most of these will remain the same between hit detection tests. */
- initializeHitPoint
{
int i;
for (i = 0; i < MAX_PTS_HIT; i++)
{
hitPoint.pts[i] = 0;
}
hitPoint.num_pts = i;
hitPoint.ops[0] = dps_setbbox;
hitPoint.ops[1] = dps_moveto;
hitPoint.ops[2] = dps_rlineto;
hitPoint.ops[3] = dps_rlineto;
hitPoint.ops[4] = dps_rlineto;
hitPoint.ops[5] = dps_closepath;
hitPoint.num_ops = 6;
return self;
}
- free
{
if (hitPoint.pts)
NX_FREE(hitPoint.pts);
if (hitPoint.ops)
NX_FREE(hitPoint.ops);
[animatorId free];
[imageId free];
return [super free];
}
- setDisplayTime:anObject
{
displayTime = anObject;
return self;
}
- toggleGstate:sender
{
totalTime = numIterations = 0;
gstatesOn = [sender state];
return self;
}
- toggleUpath:sender
{
totalTime = numIterations = 0;
upathsServer = [sender state];
return self;
}
/* Use DPSDoUserPath to draw the lines of the clock. */
static void drawUpathLines (pts, ops, clr, wid, x, y, startlen, endlen, deg)
float pts[];
char ops[];
float clr, wid, x, y, startlen, endlen, deg;
{
int i , j;
float angle;
deg = ABS(deg * RADIAN);
i = 4; j = 1;
for (angle = 0; angle < 2 * M_PI; angle += deg)
{
pts[i++] = (floor) (x + (float) cos(angle) * startlen);
pts[i++] = (floor) (y + (float) sin(angle) * startlen);
ops[j++] = dps_moveto;
pts[i++] = (floor) (x + (float) cos(angle) * endlen);
pts[i++] = (floor) (y + (float) sin(angle) * endlen);
ops[j++] = dps_lineto;
}
PSsetgray(clr);
PSsetlinewidth(wid);
DPSDoUserPath(&pts[4], i - 4, dps_float, &ops[1], j -1, pts, dps_ustroke);
}
/*
* Draw the clock face into an offscreen bitmap. Resizes the bitmap if the
* frame of this view is larger than the current size of the bitmap. Uses the
* frame dimensions instead of the bounds because the bounds are
* affected by the scale:: method and do not produce the correct
* dimensions in the default user space.
*/
- drawFace
{
char *ops;
float *pts;
NXSize size;
NXPoint center;
float maxnums, maxdashes, maxcircle;
if (imageId)
{
[imageId getSize:&size];
if (size.width < frame.size.width || size.height < frame.size.height)
[imageId setSize:&frame.size];
}
else
imageId = [[NXImage newSize:&frame.size] setFlipped:NO];
center.x = floor(bounds.size.width/2);
center.y = floor(bounds.size.height/2);
maxcircle = MIN(center.y - 10, center.x -10);
maxnums = maxcircle * SIZENUMS;
maxdashes = maxcircle * SIZEDASHES;
NX_MALLOC(pts, float, MAX_PTS);
NX_MALLOC(ops, char, MAX_OPS);
pts[0] = bounds.origin.x;
pts[1] = bounds.origin.y;
pts[2] = bounds.origin.x + bounds.size.width;
pts[3] = bounds.origin.y + bounds.size.height;
ops[0] = dps_setbbox;
[imageId lockFocus];
PSscale(frame.size.width/bounds.size.width, frame.size.height/bounds.size.height);
PSWEraseView (CLRVIEW, bounds.origin.x, bounds.origin.y,
bounds.size.width, bounds.size.height);
PSWMakeCircle(center.x, center.y, maxcircle);
PSWFillPath(CLRCIRC);
PSsetlinecap(1);
drawUpathLines(pts, ops, CLRMIN, WIDMIN, center.x, center.y,
maxdashes * LENMIN, maxdashes, DEGMIN);
drawUpathLines(pts, ops, CLRHOUR, WIDHOUR, center.x, center.y,
maxdashes * LENHOUR, maxdashes, DEGHOUR);
[imageId unlockFocus];
if (pts)
NX_FREE(pts);
if (ops)
NX_FREE(ops);
return self;
}
/* Define the userpaths of the hands as user objects. */
- defineUPaths
{
/* Setup hour hand upath. */
PSWSetUpath(ptsHour, sizeof (ptsHour)/sizeof (float),
opsHour, sizeof (opsHour)/sizeof (char));
upathHour = DPSDefineUserObject(0);
/* Setup minute hand upath. */
PSWSetUpath(ptsMin, sizeof (ptsMin)/sizeof (float),
opsMin, sizeof (opsMin)/sizeof (char));
upathMin = DPSDefineUserObject(0);
/* Setup seconds hand upath. */
PSWSetUpath(ptsSec, sizeof (ptsSec)/sizeof (float),
opsSec, sizeof (opsSec)/sizeof (char));
upathSec = DPSDefineUserObject(0);
/* Setup top of alarm hand upath. */
PSWSetUpath(ptsAlarmTop, sizeof (ptsAlarmTop)/sizeof (float),
opsAlarmTop, sizeof (opsAlarmTop)/sizeof (char));
upathAlarmTop = DPSDefineUserObject(0);
/* Setup bottom of alarm hand upath. */
PSWSetUpath(ptsAlarmBot, sizeof (ptsAlarmBot)/sizeof (float),
opsAlarmBot, sizeof (opsAlarmBot)/sizeof (char));
upathAlarmBot = DPSDefineUserObject(0);
return self;
}
/*
* If a user object has not been allocated, then a gstate has also not been
* allocated. As a result, create a gstate before defining the user object.
* If a user object exists, then copy the new gstate into the old
* structure. No need to redefine the user object because
* it still refers to the same structure. The PSpop() pops the result of
* PScurrentgstate() off of the stack.
*/
static int definegstate(gstate, offsetx, offsety, color, linewidth)
int gstate;
float offsetx, offsety, color, linewidth;
{
PSgsave();
PSWSetGstate(offsetx, offsety, color, linewidth);
if (!gstate)
{
PSgstate();
gstate = DPSDefineUserObject(gstate);
}
else
{
PScurrentgstate(gstate);
PSpop();
}
PSgrestore();
return gstate;
}
/*
* Redefine the gsates because the CTM has changed as the result
* of the scale:: method.
*/
- defineGStates
{
float angle;
NXPoint center;
struct timeval timeofDay;
struct tm *localTime;
if (window)
{
center.x = floor(bounds.size.width/2);
center.y = floor(bounds.size.height/2);
[[animatorId startEntry] resetRealTime];
[self lockFocus];
gettimeofday(&timeofDay, NULL);
localTime = localtime(&timeofDay.tv_sec);
angleHour = ((localTime->tm_hour % 12) + localTime->tm_min/60.0) * DEGHOUR;
gstateHour = definegstate (gstateHour, center.x, center.y,
CLRHANDS - 0.2, LNWIDHANDS);
angleMin = (localTime->tm_min + localTime->tm_sec/60.0) * DEGMIN;
gstateMin = definegstate(gstateMin, center.x + OFFSETHANDSX,
center.y + OFFSETHANDSY, CLRHANDS - 0.2, LNWIDHANDS);
angleSec = localTime->tm_sec * DEGMIN;
gstateSec = definegstate(gstateSec,
center.x + (2 * OFFSETHANDSX),
center.y + (2 * OFFSETHANDSY),
CLRSECOND, LNWIDSECOND);
gstateShad = definegstate(gstateShad,
center.x + (2 * OFFSETHANDSX) + OFFSETSHADX,
center.y + (2 * OFFSETHANDSY) + OFFSETSHADY,
CLRSHADOW, LNWIDSECOND);
[self unlockFocus];
}
return self;
}
/*
* This method changes the title of the menu cell according to the
* value of the trace variable.
*/
-trace:sender
{
trace = YES;
return self;
}
/* Messaged by the Animator object after a timed entry has been received. */
- tick:sender
{
angleSec = angleSec + TICKSEC;
angleMin = angleMin + TICKMIN;
angleHour = angleHour + TICKHOUR;
[self display];
return self;
}
/*
* Scales the view and then redefines the graphic states.
* The graphic state objects pick up the scaled view upon redefinition.
*/
- sizeTo:(NXCoord)width :(NXCoord)height
{
NXRect xframe;
[animatorId stopEntry];
xframe = frame;
[super sizeTo:width :height];
if (xframe.size.width && xframe.size.height)
{
[self scale:width/xframe.size.width :height/xframe.size.height];
[self drawFace];
[self defineGStates];
}
return self;
}
/* Enter a modal loop to redraw the alarm hand per mouse drag event. */
- setAlarm:(NXEvent *)event
{
int old_mask;
NXPoint p, center;
NXEvent peek;
center.x = floor(bounds.size.width/2);
center.y = floor(bounds.size.height/2);
old_mask = [window addToEventMask:NX_MOUSEUPMASK|
NX_MOUSEDRAGGEDMASK|NX_TIMERMASK];
event = [NXApp getNextEvent:NX_MOUSEUPMASK|
NX_MOUSEDRAGGEDMASK|NX_TIMERMASK];
[self lockFocus];
while (event->type != NX_MOUSEUP)
{
if (event->type == NX_TIMER)
{
angleSec = angleSec + TICKSEC;
angleMin = angleMin + TICKMIN;
angleHour = angleHour + TICKHOUR;
if (![NXApp peekNextEvent:NX_MOUSEDRAGGEDMASK into:&peek])
event = [NXApp getNextEvent:NX_MOUSEDRAGGEDMASK];
}
if (event->type == NX_MOUSEDRAGGED)
{
p = event->location;
[self convertPoint:&p fromView:nil];
angleAlarm = atan((p.y - center.y)/(p.x - center.x))/RADIAN - 90;
if (p.x - center.x < 0)
angleAlarm -= 180;
if ([NXApp peekNextEvent:NX_TIMERMASK into:&peek
waitFor:0 threshold:NX_BASETHRESHOLD])
{
angleSec = angleSec + TICKSEC;
angleMin = angleMin + TICKMIN;
angleHour = angleHour + TICKHOUR;
event = [NXApp getNextEvent:NX_TIMERMASK];
}
}
PSgsave();
[self drawSelf:&bounds :1];
PSgrestore();
[window flushWindow];
NXPing();
event = [NXApp getNextEvent:NX_MOUSEUPMASK|NX_MOUSEDRAGGEDMASK|NX_TIMERMASK
waitFor:1000 threshold:NX_BASETHRESHOLD];
}
[window setEventMask:old_mask];
return self;
}
/* Set the hit detection userpath upon a mouse down. */
- setHitPoint:(const NXPoint *)p
{
float z;
NXPoint pt;
pt.x = p->x - floor(bounds.size.width/2);
pt.y = p->y - floor(bounds.size.height/2);
z = sqrt (pt.x * pt.x + pt.y * pt.y);
pt.x = pt.x - (z * sin(ABS(angleAlarm) * RADIAN));
pt.y = pt.y + z - z * cos(ABS(angleAlarm) * RADIAN);
/* Bounding Box */
hitPoint.pts[0] = floor(pt.x - HITSETTING/2);
hitPoint.pts[1] = floor(pt.y - HITSETTING/2);
hitPoint.pts[2] = ceil(pt.x + HITSETTING/2);
hitPoint.pts[3] = ceil(pt.y + HITSETTING/2);
/* Moveto */
hitPoint.pts[4] = pt.x - HITSETTING/2;
hitPoint.pts[5] = pt.y - HITSETTING/2;
/* Rlineto's */
hitPoint.pts[7] = HITSETTING;
hitPoint.pts[8] = HITSETTING;
hitPoint.pts[11] = -HITSETTING;
return self;
}
/*
* Check for hit detection. No boundary check is made because the
* alarm hand can reside in pretty much the whole view.
*/
- (BOOL) isHit:(const NXPoint *) p
{
int hit;
[self setHitPoint:p];
PSgsave();
PSWInstallGstate(gstateHour, angleAlarm);
PSWHitPath(upathAlarmTop, upathAlarmBot, hitPoint.pts,
hitPoint.num_pts, hitPoint.ops, hitPoint.num_ops, &hit);
PSgrestore();
return (BOOL) hit;
}
/* This method handles a mouse down. */
- mouseDown:(NXEvent *)event
{
NXPoint p;
p = event->location;
[self convertPoint:&p fromView:nil];
if ([self isHit:&p])
[self setAlarm:event];
return self;
}
/*
* Either draws with gstates or doesn't. A slight performance advantage is
* gained with gstates but they use up an appreciable amount of memory
* so they should be used judiciously.
*/
- setStateAndDraw
{
NXPoint center;
if (gstatesOn)
{
PSWInstallGstate(gstateHour, angleAlarm);
drawClockHand(self, ALARM);
PSWInstallGstate(gstateHour, angleHour);
drawClockHand(self, HOUR);
PSWInstallGstate(gstateMin, angleMin);
drawClockHand(self, MINUTE);
PSWInstallGstate(gstateShad, angleSec);
drawClockHand(self, SHADOW);
PSWInstallGstate(gstateSec, angleSec);
drawClockHand(self, SECOND);
}
else
{
center.x = floor(bounds.size.width/2);
center.y = floor(bounds.size.height/2);
PSgsave();
PStranslate(center.x, center.y);
PSrotate(angleAlarm);
drawClockHand(self, ALARM);
PSgrestore();
PSgsave();
PSsetgray(CLRHANDS - 0.2);
PSsetlinewidth(LNWIDHANDS);
PStranslate(center.x , center.y);
PSrotate(angleHour);
drawClockHand(self, HOUR);
PSgrestore();
PSgsave();
PSsetgray(CLRHANDS - 0.2);
PSsetlinewidth(LNWIDHANDS);
PStranslate(center.x + OFFSETHANDSX,
center.y + OFFSETHANDSY);
PSrotate(angleMin);
drawClockHand(self, MINUTE);
PSgrestore();
PSgsave();
PSsetgray(CLRSHADOW);
PStranslate(center.x + (2*OFFSETHANDSX) + OFFSETSHADX,
center.y + (2*OFFSETHANDSY) + OFFSETSHADY);
PSrotate(angleSec);
drawClockHand(self, SHADOW);
PSgrestore();
PSgsave();
PSsetgray(CLRSECOND);
PSsetlinewidth(LNWIDSECOND);
PStranslate(center.x + OFFSETHANDSX + OFFSETSHADX,
center.y + OFFSETHANDSY + OFFSETSHADY);
PSrotate(angleSec);
drawClockHand(self, SECOND);
PSgrestore();
}
}
/*
* Draws the clock hands either as stored in the server
* or by sending them each time.
*/
static void drawClockHand(id self, int hand)
{
if (self->upathsServer)
{
switch(hand)
{
case ALARM:
PSsetgray(CLRALARMTOP);
PSWUpathFill(self->upathAlarmTop);
PSsetgray(CLRALARMBOT);
PSWUpathFill(self->upathAlarmBot);
break;
case HOUR:
PSWUpathStrokeFill(self->upathHour);
break;
case MINUTE:
PSWUpathStrokeFill(self->upathMin);
break;
case SHADOW:
PSWUpathFill(self->upathSec);
break;
case SECOND:
PSWUpathFill(self->upathSec);
PSWDrawCircle(CLRSECOND - 0.2);
break;
}
}
else
{
switch(hand)
{
case ALARM:
PSsetgray(CLRALARMTOP);
DPSDoUserPath(&ptsAlarmTop[4], sizeof (ptsAlarmTop)/sizeof (float) - 4,
dps_float, &opsAlarmTop[1], sizeof (opsAlarmTop)/sizeof (char) -1,
ptsAlarmTop, dps_ufill);
PSsetgray(CLRALARMBOT);
DPSDoUserPath(&ptsAlarmBot[4], sizeof (ptsAlarmBot)/sizeof (float) - 4,
dps_float, &opsAlarmBot[1], sizeof (opsAlarmBot)/sizeof (char) - 1,
ptsAlarmBot, dps_ufill);
break;
case HOUR:
DPSDoUserPath(&ptsHour[4], sizeof (ptsHour)/sizeof (float) - 4,
dps_float, &opsHour[1], sizeof (opsHour)/sizeof (char) - 1,
ptsHour, dps_ustroke);
PSsetgray(CLRHANDS);
DPSDoUserPath(&ptsHour[4], sizeof (ptsHour)/sizeof (float) - 4,
dps_float, &opsHour[1], sizeof (opsHour)/sizeof (char) - 1,
ptsHour, dps_ufill);
break;
case MINUTE:
DPSDoUserPath(&ptsMin[4], sizeof (ptsMin)/sizeof (float) - 4,
dps_float, &opsMin[1], sizeof (opsMin)/sizeof (char) - 1,
ptsMin, dps_ustroke);
PSsetgray(CLRHANDS);
DPSDoUserPath(&ptsMin[4], sizeof (ptsMin)/sizeof (float) - 4,
dps_float, &opsMin[1], sizeof (opsMin)/sizeof (char) - 1,
ptsMin, dps_ufill);
break;
case SHADOW:
DPSDoUserPath(&ptsSec[4], sizeof (ptsSec)/sizeof (float) - 4,
dps_float, &opsSec[1], sizeof (opsSec)/sizeof (char) - 1,
ptsSec, dps_ufill);
break;
case SECOND:
DPSDoUserPath(&ptsSec[4], sizeof (ptsSec)/sizeof (float) - 4,
dps_float, &opsSec[1], sizeof (opsSec)/sizeof (char) - 1,
ptsSec, dps_ufill);
PSWDrawCircle(CLRSECOND - 0.2);
break;
}
}
}
- drawSelf:(NXRect *)r :(int) count
{
int ElapsedTime;
[displayTime setStringValue:""];
PSWMarkTime (); NXPing ();
if (trace)
DPSTraceContext(DPSGetCurrentContext(), YES);
[imageId composite:NX_COPY toPoint:&bounds.origin];
[self setStateAndDraw];
if (trace)
DPSTraceContext(DPSGetCurrentContext(), NO);
PSWReturnTime (&ElapsedTime);
trace = NO;
totalTime += ElapsedTime;
++numIterations;
[displayTime setIntValue:(totalTime/numIterations)];
return self;
}
@end